package com.anlu.base.common.security.component;

import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 主要职责是解析 Bearer Token。Bearer Token 是一种在 OAuth 2.0 等
 * 身份验证协议中广泛使用的访问令牌（Access Token）类型。当客户端向服务器发送请求时，
 * 通常会在请求的 Authorization 头中包含 Bearer <token> 这样的信息，
 * 其中 <token> 是代表用户或客户端权限的令牌。BearerTokenResolver
 * 的任务就是从请求的 Authorization 头中提取出这个令牌
 *
 * 总结：
 * BearerTokenResolver 是身份验证和授权机制中一个重要的组件，它通过解析和验证 Bearer Token
 * 确保请求的安全性和合法性，并且帮助系统进行权限控制和资源访问的决策。
 */
public class PigBearerTokenExtractor implements BearerTokenResolver {

    private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
            Pattern.CASE_INSENSITIVE);
    private boolean allowFormEncodedBodyParameter = false;

    private boolean allowUriQueryParameter = true;

    private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;

    private final PathMatcher pathMatcher = new AntPathMatcher();

    private final PermitAllUrlProperties urlProperties;

    public PigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
        this.urlProperties = urlProperties;
    }

    @Override
    public String resolve(HttpServletRequest request) {
        boolean match = urlProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
        if(match){
            return null;
        }
        final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
        final String parameterToken = isParameterTokenSupportedForRequest(request) ? resolveFromRequestParameters(request) : null;
        if(authorizationHeaderToken != null){
            if(parameterToken != null){
                final BearerTokenError error =BearerTokenErrors.invalidToken("Found multiple bearer tokens in the request");
                throw new OAuth2AuthenticationException(error);
            }
            return authorizationHeaderToken;
        }
        if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
            return parameterToken;
        }
        return null;
    }

    private String resolveFromAuthorizationHeader(HttpServletRequest request){
        String authorization = request.getHeader(this.bearerTokenHeaderName);
        if(!StringUtils.startsWithIgnoreCase(authorization,"bearer")){
            return null;
        }
        Matcher matcher = authorizationPattern.matcher(authorization);
        if(!matcher.matches()){
            BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
            throw new OAuth2AuthenticationException(error);
        }
        return matcher.group("token");
    }

    private static String resolveFromRequestParameters(HttpServletRequest request){
        String[] values = request.getParameterValues("access_token");
        if(values == null  || values.length == 0){
            return null;
        }

        if (values.length == 1) {
            return values[0];
        }
        BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
        throw new OAuth2AuthenticationException(error);
    }

    private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request){
        return (("POST".equals(request.getMethod())
                && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
                || "GET".equals(request.getMethod()));
    }

    private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {
        return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod())
                && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
                || (this.allowUriQueryParameter && "GET".equals(request.getMethod())));
    }
}
